/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xerces.internal.impl.dv.xs;

import com.sun.org.apache.xerces.internal.impl.dv.InvalidDatatypeValueException;
import com.sun.org.apache.xerces.internal.util.URI;
import com.sun.org.apache.xerces.internal.impl.dv.ValidationContext;

/**
 * Represent the schema type "anyURI"
 *
 * @author Neeraj Bajaj, Sun Microsystems, inc.
 * @author Sandy Gao, IBM
 * @xerces.internal
 */
public class AnyURIDV extends TypeValidator {

  private static final URI BASE_URI;

  static {
    URI uri = null;
    try {
      uri = new URI("abc://def.ghi.jkl");
    } catch (URI.MalformedURIException ex) {
    }
    BASE_URI = uri;
  }

  public short getAllowedFacets() {
    return (XSSimpleTypeDecl.FACET_LENGTH | XSSimpleTypeDecl.FACET_MINLENGTH
        | XSSimpleTypeDecl.FACET_MAXLENGTH | XSSimpleTypeDecl.FACET_PATTERN
        | XSSimpleTypeDecl.FACET_ENUMERATION | XSSimpleTypeDecl.FACET_WHITESPACE);
  }

  // before we return string we have to make sure it is correct URI as per spec.
  // for some types (string and derived), they just return the string itself
  public Object getActualValue(String content, ValidationContext context)
      throws InvalidDatatypeValueException {
    // check 3.2.17.c0 must: URI (rfc 2396/2723)
    try {
      if (content.length() != 0) {
        // encode special characters using XLink 5.4 algorithm
        final String encoded = encode(content);
        // Support for relative URLs
        // According to Java 1.1: URLs may also be specified with a
        // String and the URL object that it is related to.
        new URI(BASE_URI, encoded);
      }
    } catch (URI.MalformedURIException ex) {
      throw new InvalidDatatypeValueException("cvc-datatype-valid.1.2.1",
          new Object[]{content, "anyURI"});
    }

    // REVISIT: do we need to return the new URI object?
    return content;
  }

  // which ASCII characters need to be escaped
  private static boolean gNeedEscaping[] = new boolean[128];
  // the first hex character if a character needs to be escaped
  private static char gAfterEscaping1[] = new char[128];
  // the second hex character if a character needs to be escaped
  private static char gAfterEscaping2[] = new char[128];
  private static char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7',
      '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

  // initialize the above 3 arrays
  static {
    for (int i = 0; i <= 0x1f; i++) {
      gNeedEscaping[i] = true;
      gAfterEscaping1[i] = gHexChs[i >> 4];
      gAfterEscaping2[i] = gHexChs[i & 0xf];
    }
    gNeedEscaping[0x7f] = true;
    gAfterEscaping1[0x7f] = '7';
    gAfterEscaping2[0x7f] = 'F';
    char[] escChs = {' ', '<', '>', '"', '{', '}',
        '|', '\\', '^', '~', '`'};
    int len = escChs.length;
    char ch;
    for (int i = 0; i < len; i++) {
      ch = escChs[i];
      gNeedEscaping[ch] = true;
      gAfterEscaping1[ch] = gHexChs[ch >> 4];
      gAfterEscaping2[ch] = gHexChs[ch & 0xf];
    }
  }

  // To encode special characters in anyURI, by using %HH to represent
  // special ASCII characters: 0x00~0x1F, 0x7F, ' ', '<', '>', etc.
  // and non-ASCII characters (whose value >= 128).
  private static String encode(String anyURI) {
    int len = anyURI.length(), ch;
    StringBuffer buffer = new StringBuffer(len * 3);

    // for each character in the anyURI
    int i = 0;
    for (; i < len; i++) {
      ch = anyURI.charAt(i);
      // if it's not an ASCII character, break here, and use UTF-8 encoding
      if (ch >= 128) {
        break;
      }
      if (gNeedEscaping[ch]) {
        buffer.append('%');
        buffer.append(gAfterEscaping1[ch]);
        buffer.append(gAfterEscaping2[ch]);
      } else {
        buffer.append((char) ch);
      }
    }

    // we saw some non-ascii character
    if (i < len) {
      // get UTF-8 bytes for the remaining sub-string
      byte[] bytes = null;
      byte b;
      try {
        bytes = anyURI.substring(i).getBytes("UTF-8");
      } catch (java.io.UnsupportedEncodingException e) {
        // should never happen
        return anyURI;
      }
      len = bytes.length;

      // for each byte
      for (i = 0; i < len; i++) {
        b = bytes[i];
        // for non-ascii character: make it positive, then escape
        if (b < 0) {
          ch = b + 256;
          buffer.append('%');
          buffer.append(gHexChs[ch >> 4]);
          buffer.append(gHexChs[ch & 0xf]);
        } else if (gNeedEscaping[b]) {
          buffer.append('%');
          buffer.append(gAfterEscaping1[b]);
          buffer.append(gAfterEscaping2[b]);
        } else {
          buffer.append((char) b);
        }
      }
    }

    // If encoding happened, create a new string;
    // otherwise, return the orginal one.
    if (buffer.length() != len) {
      return buffer.toString();
    } else {
      return anyURI;
    }
  }

} // class AnyURIDV
